﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Net;
using LiveNet.Authentication;
using System.Drawing;
using System.Globalization;

namespace LiveNet.Contacts
{
    public sealed partial class LiveContacts : LiveNetBase<DefaultRequestCreator, DefaultAuthenticationDetails>
    {
        #region Private Data
        private string m_lid = "";
        private Uri m_uri;
        private string m_token;
        private XmlDocument m_xmldoc;
        private string m_xml = "";
        private string m_location = "";
        private Owner m_owner = new Owner();
        private ContactCollection m_contacts = new ContactCollection();
        private TagCollection m_tags = new TagCollection();
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LiveContacts"/> class.
        /// </summary>
        /// <param name="id">The user id.</param>
        /// <param name="token">The token.</param>
        /// <param name="tokenType">Type of the token.</param>
        public LiveContacts(string id, string token, AuthenticationToken tokenType)
            : base(new DefaultAuthenticationDetails(id, token, tokenType))
        {
            m_token = token;
            if (tokenType == AuthenticationToken.DelegatedToken)
            {
                long llid = Int64.Parse(id, System.Globalization.NumberStyles.HexNumber);
                m_RootPath = new Uri(string.Format("https://livecontacts.services.live.com/users/@C@{0}/rest/livecontacts", Int64.Parse(id, System.Globalization.NumberStyles.HexNumber).ToString()));
                m_InvitationPath = new Uri(string.Format("https://livecontacts.services.live.com/users/@C@{0}/rest/invitationsbyemail", Int64.Parse(id, System.Globalization.NumberStyles.HexNumber).ToString()));
            }
            else
            {
                m_RootPath = new Uri(string.Format("https://cumulus.services.live.com/{0}/LiveContacts", id));
            }
        }
        #endregion

        #region Public Methods
        #region Retrieval Methods
        public void GetInvitations()
        {
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(InvitationPath.ToString()),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            ContactsOwner.FromXml(xmlDoc.SelectSingleNode("LiveContacts/Owner"));
            Contacts.FromXml(xmlDoc.SelectSingleNode("LiveContacts/Contacts"));
        }

        public void GetAll()
        {
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString()),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            ContactsOwner.FromXml(xmlDoc.SelectSingleNode("LiveContacts/Owner"));
            Contacts.FromXml(xmlDoc.SelectSingleNode("LiveContacts/Contacts"));
            Tags.FromXml(xmlDoc.SelectSingleNode("LiveContacts/Tags"));
        }

        public void GetOwner()
        {
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + "/Owner"),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            ContactsOwner.FromXml(xmlDoc.SelectSingleNode("Owner"));
        }

        public void GetContacts()
        {
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + "/Contacts"),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Contacts.FromXml(xmlDoc.SelectSingleNode("Contacts"));
        }

        public Contact GetContact(Guid contactID)
        {
            string relativeURI = "/Contacts/contact(" + contactID.ToString() + ")";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Contact contact = Contacts.Find(contactID);
            if (contact == null) return null;
            contact.FromXml(xmlDoc.SelectSingleNode("Contact"));
            return contact;
        }

        public void GetTags()
        {
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + "/Tags"),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Tags.FromXml(xmlDoc.SelectSingleNode("Tags"));
        }

        public Tag GetTag(Guid tagID)
        {
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + "/Tags/tag(" + tagID.ToString() + ")"),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Tag tag = Tags.Find(tagID);
            if (tag == null) return null;
            tag.FromXml(xmlDoc.SelectSingleNode("Tag"));
            return tag;
        }

        public void GetEmails(Contact contact)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/emails";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
            delegate(string xml)
            {
                xmlDoc.LoadXml(xml);
            });
            contact.Emails.FromXml(xmlDoc.SelectSingleNode("Emails"));
        }

        public Email GetEmail(Contact contact, int emailID)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/emails/email(" + emailID.ToString() + ")";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Email email = contact.Emails.Find(emailID);
            if (email == null) return null;
            email.FromXml(xmlDoc.SelectSingleNode("Email"));
            return email;
        }

        public void GetPhones(Contact contact)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/phones";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            contact.Phones.FromXml(xmlDoc.SelectSingleNode("Phones"));
       }

        public Phone GetPhone(Contact contact, int phoneID)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/phones/phone(" + phoneID.ToString() + ")";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Phone phone = contact.Phones.Find(phoneID);
            if (phone == null) return null;
            phone.FromXml(xmlDoc.SelectSingleNode("Phone"));
            return phone;
        }

        public void GetLocations(Contact contact)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/locations";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            contact.Locations.FromXml(xmlDoc.SelectSingleNode("Locations"));
        }

        public Location GetLocation(Contact contact, int locationID)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/locations/location(" + locationID.ToString() + ")";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            Location location = contact.Locations.Find(locationID);
            if (location == null) return null;
            location.FromXml(xmlDoc.SelectSingleNode("Location"));
            return location;
        }

        public void GetURIs(Contact contact)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/uris";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            contact.URIs.FromXml(xmlDoc.SelectSingleNode("URIs"));
        }

        public CUri GetURI(Contact contact, int uriID)
        {
            string relativeURI = "/Contacts/contact(" + contact.ID.ToString() + ")/uris/uri(" + uriID.ToString() + ")";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            CUri uri = contact.URIs.Find(uriID);
            if (uri == null) return null;
            uri.FromXml(xmlDoc.SelectSingleNode("URI"));
            return uri;
        }

        public void GetContactIDs(Tag tag)
        {
            string relativeURI = "/tags/tag(" + tag.ID.ToString() + ")/contactIDs";
            XmlDocument xmlDoc = new XmlDocument();
            MakeGetRequest(new Uri(RootPath.ToString() + relativeURI),
                delegate(string xml)
                {
                    xmlDoc.LoadXml(xml);
                });
            tag.LoadContactIDs(xmlDoc.SelectSingleNode("ContactIDs"));
        }
        #endregion

        #region Add Methods
        public void AddContact(Contact contact)
        {
            MakePostRequest(new Uri(RootPath.ToString() + "/contacts"), contact.ToXml(OperationType.Create).InnerXml);
            // Get the contact ID returned in the Location header.
            contact.ID = new Guid(GetNewItemID());
            Contacts.Add(contact);
        }

        public void AddEmail(Contact contact, Email email)
        {
            MakePostRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/emails"), email.ToXml(OperationType.Create).InnerXml);
            // Get the email ID returned in the Location header.
            email.ID = Convert.ToInt32(GetNewItemID());
            contact.Emails.Add(email);
        }

        public void AddPhone(Contact contact, Phone phone)
        {
            MakePostRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/phones"), phone.ToXml(OperationType.Create).InnerXml);
            // Get the phone ID returned in the Location header.
            phone.ID = Convert.ToInt32(GetNewItemID());
            contact.Phones.Add(phone);
        }

        public void AddLocation(Contact contact, Location location)
        {
            MakePostRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/locations"), location.ToXml(OperationType.Create).InnerXml);
            // Get the location ID returned in the Location header.
            location.ID = Convert.ToInt32(GetNewItemID());
            contact.Locations.Add(location);
        }

        public void AddURI(Contact contact, CUri uri)
        {
            MakePostRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/uris"), uri.ToXml(OperationType.Create).InnerXml);
            // Get the uri ID returned in the Location header.
            uri.ID = Convert.ToInt32(GetNewItemID());
            contact.URIs.Add(uri);
        }

        public void AddContactID(Tag tag, Guid contactID)
        {
            MakePostRequest(new Uri(RootPath.ToString() + "/tags/tag(" + tag.ID.ToString() + ")/contactIDs"), "<ContactID>" + contactID.ToString() + "</ContactID>");
            tag.ContactIDs.Add(contactID);
        }
        #endregion

        #region Update Methods
        public void UpdateOwner()
        {
            // *** Unsupported in beta 1.0 ***//
            throw new Exception("Unsupported in beta 1.0");
            MakePutRequest(new Uri(RootPath.ToString() + "/Owner"), ContactsOwner.ToXml(OperationType.Update).InnerXml);
        }

        public void UpdateContact(Contact contact)
        {
            MakePutRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")"), contact.ToXml(OperationType.Update).InnerXml);
        }

        public void UpdateEmail(Contact contact, Email email)
        {
            MakePutRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/emails/email(" + email.ID.ToString() + ")"), email.ToXml(OperationType.Update).InnerXml);
        }

        public void UpdatePhone(Contact contact, Phone phone)
        {
            MakePutRequest(m_uri = new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/phones/phone(" + phone.ID.ToString() + ")"), phone.ToXml(OperationType.Update).InnerXml);
        }

        public void UpdateLocation(Contact contact, Location location)
        {
            MakePutRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/locations/location(" + location.ID.ToString() + ")"), location.ToXml(OperationType.Update).InnerXml);
        }

        public void UpdateURI(Contact contact, CUri uri)
        {
            MakePutRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/uris/uri(" + uri.ID.ToString() + ")"), uri.ToXml(OperationType.Update).InnerXml);
        }

        public void UpdateTag(Tag tag)
        {
            MakePutRequest(new Uri(RootPath.ToString() + "/tags/tag(" + tag.ID.ToString() + ")"), tag.ToXml(OperationType.Update).InnerXml);
        }
        #endregion

        #region Delete Methods
        public void DeleteContact(Contact contact)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")"));
            Contacts.Remove(contact);
        }

        public void DeleteEmail(Contact contact, Email email)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/emails/email(" + email.ID.ToString() + ")"));
            contact.Emails.Remove(email);
        }

        public void DeletePhone(Contact contact, Phone phone)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/phones/phone(" + phone.ID.ToString() + ")"));
            contact.Phones.Remove(phone);
        }

        public void DeleteLocation(Contact contact, Location location)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/locations/location(" + location.ID.ToString() + ")"));
            contact.Locations.Remove(location);
        }

        public void DeleteURI(Contact contact, CUri uri)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/contacts/contact(" + contact.ID.ToString() + ")/uris/uri(" + uri.ID.ToString() + ")"));
            contact.URIs.Remove(uri);
        }

        public void DeleteTag(Tag tag)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/tags/tag(" + tag.ID.ToString() + ")"));
            Tags.Remove(tag);
        }

        public void DeleteContactID(Tag tag, Guid contactID)
        {
            MakeDeleteRequest(new Uri(RootPath.ToString() + "/tags/tag(" + tag.ID.ToString() + ")/ContactIDs/contactid(" + contactID.ToString() + ")"));
            tag.Remove(contactID.ToString());
        }
        #endregion
        #endregion

        #region Public Properties
        public string RootPath
        {
            get { return m_RootPath.ToString(); }
        }

        public string InvitationPath
        {
            get { return this.m_InvitationPath.ToString(); }
        }

        public Uri CurrentURI
        {
            get { return m_uri; }
        }

        public string XML
        {
            get { return m_xml; }
        }

        public string Location
        {
            get { return m_location; }
        }

        public Owner ContactsOwner
        {
            get { return m_owner; }
        }

        public ContactCollection Contacts
        {
            get { return m_contacts; }
        }

        public TagCollection Tags
        {
            get { return m_tags; }
        }
        #endregion

        #region Private Methods
        private string GetNewItemID()
        {
            int indexBegin = Location.LastIndexOf("(");
            int indexEnd = Location.LastIndexOf(")");
            if (indexBegin > -1 && indexEnd > -1)
            {
                return Location.Substring(indexBegin + 1, indexEnd - indexBegin - 1);
            }
            return "";
        }
        #endregion
    }
}